package org.springframework.boot.autoconfigure.dubbo.configuration;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.dubbo.annotation.AutowiredDubbo;
import org.springframework.boot.autoconfigure.dubbo.annotation.EnableDubbo;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.ReferenceBean;

/**
 * dubbo消费者的自动装配
 * 
 * @author wolfking@赵伟伟
 * @mail zww199009@163.com
 * @创作日期 2017年4月18日 下午9:56:05
 * @copyright wolfking
 */
@Configuration
@ConditionalOnBean(annotation = EnableDubbo.class)
@AutoConfigureAfter(DubboAutoConfiguration.class)
@EnableConfigurationProperties(DubboConfiguration.class)
public class ConsumerAutoConfiguration implements BeanPostProcessor {
	private Logger logger = LoggerFactory.getLogger(ConsumerAutoConfiguration.class);
	private Map<String, Object> refrences = new ConcurrentHashMap<String, Object>();
	@Autowired
	private ApplicationContext applicationContext;

	@Autowired
	private DubboConfiguration configuration;

	@Autowired(required = false)
	private ApplicationConfig applicationConfig;
	@Autowired(required = false)
	private RegistryConfig registryConfig;

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		Object current = bean;
		Class<?> clazz = bean.getClass();
		if (AopUtils.isAopProxy(bean))
			clazz = AopUtils.getTargetClass(bean);
		try {
			if (AopUtils.isCglibProxy(bean))
				current = getCglibProxyTargetObject(bean);
			else if (AopUtils.isJdkDynamicProxy(bean))
				current = getJdkDynamicProxyTargetObject(bean);
		} catch (Exception e) {
			logger.error("", e);
		}
		try {
			for (Field field : clazz.getDeclaredFields()) {
				AutowiredDubbo autowiredDubbo = field.getAnnotation(AutowiredDubbo.class);
				if (autowiredDubbo != null) {
					Class<?> type = field.getType();
					ReferenceBean<?> dubboConsumer = initAutowiredDubboBean(type, autowiredDubbo);
					String group = dubboConsumer.getGroup();
					String version = dubboConsumer.getVersion();
					String key = type + "_" + group + "_" + version;
					Object dubboReference = refrences.get(key);
					if (dubboReference == null) {
						synchronized (this) {
							dubboReference = refrences.get(key);
							if (dubboReference == null) {
								dubboConsumer.setApplicationContext(applicationContext);
								dubboConsumer.setApplication(applicationConfig);
								RegistryConfig registry = dubboConsumer.getRegistry();
								if (registry == null)
									dubboConsumer.setRegistry(registryConfig);
								dubboConsumer.setApplication(applicationConfig);
								dubboConsumer.afterPropertiesSet();
								dubboReference = dubboConsumer.getObject();
								refrences.put(key, dubboReference);
							}
						}
					}
					field.setAccessible(true);
					field.set(current, dubboReference);
					field.setAccessible(false);
				}
			}
		} catch (Exception e) {
			throw new BeanCreationException(beanName, e);
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

		return bean;
	}

	/**
	 * 获取dubbo消费者的实体
	 * 
	 * @param interfaceClazz
	 * @param dubbo
	 * @return
	 * @throws BeansException
	 */
	private <T> ReferenceBean<T> initAutowiredDubboBean(Class<T> interfaceClazz, AutowiredDubbo dubbo) throws BeansException {
		ReferenceBean<T> consumer = new ReferenceBean<T>();
		consumer.setInterface(interfaceClazz);
		String canonicalName = interfaceClazz.getCanonicalName();
		consumer.setId(canonicalName);
		String address = dubbo.address();
		if (StringUtils.isNotBlank(address)) {
			RegistryConfig registryConfig = new RegistryConfig();
			registryConfig.setAddress(address);
			consumer.setRegistry(registryConfig);
		}
		String group = dubbo.group();
		group = StringUtils.isNotBlank(group) ? group : configuration.getGroup();
		if (StringUtils.isNotBlank(group))
			consumer.setGroup(group);
		String version = dubbo.version();
		version = StringUtils.isNotBlank(version) ? version : configuration.getVersion();
		if (StringUtils.isNotBlank(version))
			consumer.setVersion(version);
		int timeout = dubbo.timeout();
		consumer.setTimeout(timeout);
		String client = dubbo.client();
		consumer.setClient(client);
		String url = dubbo.url();
		consumer.setUrl(url);
		String protocol = dubbo.protocol();
		consumer.setProtocol(protocol);
		boolean check = dubbo.check();
		consumer.setCheck(check);
		boolean lazy = dubbo.lazy();
		consumer.setLazy(lazy);
		int retries = dubbo.retries();
		consumer.setRetries(retries);
		int actives = dubbo.actives();
		consumer.setActives(actives);
		String loadbalance = dubbo.loadbalance();
		consumer.setLoadbalance(loadbalance);
		boolean async = dubbo.async();
		consumer.setAsync(async);
		boolean sent = dubbo.sent();
		consumer.setSent(sent);
		String mock = dubbo.mock();
		consumer.setMock(mock);
		return consumer;
	}

	private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
		Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
		h.setAccessible(true);
		Object dynamicAdvisedInterceptor = h.get(proxy);
		Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
		advised.setAccessible(true);
		Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();

		return target;
	}

	private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
		Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
		h.setAccessible(true);
		AopProxy aopProxy = (AopProxy) h.get(proxy);
		Field advised = aopProxy.getClass().getDeclaredField("advised");
		advised.setAccessible(true);
		Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
		return target;
	}
}
